package visualization;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.LineArray;
import javax.media.j3d.LineAttributes;
import javax.media.j3d.Locale;
import javax.media.j3d.Node;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.media.j3d.ViewPlatform;
import javax.media.j3d.VirtualUniverse;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.vecmath.Color3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.utils.geometry.Text2D;
import com.sun.j3d.utils.universe.SimpleUniverse;

public abstract class J3DView extends JPanel {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3165387513535865167L;

	
	private TransformGroup scene;
	public Canvas3D canvas;
	
	// Current camera position
	private Vector3f cameraPos = new Vector3f(0f, 0.3f, 2f);
	// Camera position in beginning of drag
  	private Vector3f cameraPosDrag = null;  

    private TransformGroup viewTG ;
    
    private void updateCameraPosition()
    {
		Transform3D viewTransform = new Transform3D();
    	viewTransform.setTranslation(cameraPos);
    	viewTG.setTransform(viewTransform);
    	
		canvas.repaint();
    }
    
	public void zoomPerformed(ActionEvent e) {
		String cmd = e.getActionCommand();
		if (cmd.equalsIgnoreCase("ZOOM.IN")) {
			cameraPos.z -= 0.2;
			if (cameraPos.z < 0.6) cameraPos.z = 0.6f; // Limit zoom level
	//		System.out.println(cameraPos);
			updateCameraPosition();
		}
		else if (cmd.equalsIgnoreCase("ZOOM.OUT")) {
			cameraPos.z += 0.2;
			if (cameraPos.z > 10) cameraPos.z = 10f; // Limit zoom level
	//		System.out.println(cameraPos);
			updateCameraPosition();
		}
	}
	ActionListener zoomListener = new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent arg0) { zoomPerformed(arg0); }
	};
	
	Point dragStart, dragEnd;
	
	protected void dragUpdate()
	{
		if (dragStart == null || dragEnd == null) return;
		double dx = dragEnd.getX() - dragStart.getX();
		double dy = dragEnd.getY() - dragStart.getY();
	//	System.out.println("dx " + dx + ", dy " + dy);
		
		// scale in relation to zoom level (= camera z)
		double scaleFactor = Math.pow(2,cameraPos.z) / 1000.0;
		
		// update camera pos relative to drag start
		cameraPos.x = cameraPosDrag.getX() - (float)(dx*scaleFactor);
		cameraPos.y = cameraPosDrag.getY() + (float)(dy*scaleFactor);
		
	//	System.out.println("Camera " + cameraPosDrag + " -> " + cameraPos);
		updateCameraPosition();
	}
	
	/**
	 * 
	 * Add child to scene graph.
	 * 
	 * @param child
	 */
	public void addToScene(Node child)
	{
		scene.addChild(child);
	}

	/**
	 * Remove everything from the scene
	 */
	public void clearScene()
	{
		scene.removeAllChildren();
	}

	/**
	 * Init Java3D
	 */
	private void initJ3D()
	{
		// Java 3D
        GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration();
        canvas = new Canvas3D(gc);
        canvas.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
        add(canvas, BorderLayout.CENTER);
		
        BranchGroup view = createView(canvas);
        BranchGroup root = createSceneGraph();
        
        // xx
//        scene.compile();

  //      SimpleUniverse univ = new SimpleUniverse(canvas);
//        univ.getViewingPlatform().setNominalViewingTransform();

        VirtualUniverse universe = new VirtualUniverse();
        Locale locale = new Locale(universe);
        locale.addBranchGraph(view);
        locale.addBranchGraph(root);
	}

	/**
	 * Add GUI listeners
	 */
	private void addListeners()
	{
        canvas.addMouseListener(new MouseAdapter() {
        	public void mousePressed(MouseEvent e) {
        		dragStart = e.getPoint();
        		if (cameraPosDrag == null) { 
        			cameraPosDrag = new Vector3f(cameraPos);
        		}
//        		System.out.println("Start " + dragStart + " (camera " + cameraPosDrag + ")");
        	};
        	public void mouseReleased(MouseEvent e) {
        		dragEnd = e.getPoint();
//        		System.out.println("End " + dragEnd);
        		dragUpdate();
        		dragEnd = null; dragStart = null; cameraPosDrag = null;
        	};
		});
        canvas.addMouseMotionListener(new MouseMotionAdapter() {
        	@Override
        	public void mouseDragged(MouseEvent e) {
        		if (dragStart != null && dragStart.distance(e.getPoint()) > 10) {
        			dragEnd = e.getPoint();
            		dragUpdate();
        		} 
        	}
		});
		
	}

	private JToolBar toolbar;
	
	public AbstractButton addCustomToolbarButton(ImageIcon icon, String text, String toolTip, String actionCmd, ActionListener listener, Class<? extends AbstractButton> btnClass) 
	{
		try {
	        AbstractButton button = btnClass.newInstance();
	        if (icon != null) { button.setIcon(icon); }
	        else { button.setText(text); }
	        button.setActionCommand(actionCmd);
	        button.addActionListener(listener);
	        button.setToolTipText(toolTip);
	        toolbar.add(button);
	        return button;		
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public JButton addCustomToolbarButton(ImageIcon icon, String toolTip, String actionCmd, ActionListener listener)
	{
		return (JButton) addCustomToolbarButton(icon, null, toolTip, actionCmd, listener, JButton.class);
	}
	
	/**
	 * 
	 * Create toolbar
	 */
	private void createToolbar()
	{
        toolbar = new JToolBar(JToolBar.VERTICAL);
        toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.Y_AXIS));
        toolbar.setAlignmentY(JToolBar.TOP_ALIGNMENT);
        
        JButton zoomIn = new JButton();
        zoomIn.setIcon(new ImageIcon("icons/ZoomIn24.gif"));
        zoomIn.setActionCommand("ZOOM.IN");
        zoomIn.addActionListener(zoomListener);
        zoomIn.setToolTipText("Zoom In");
        toolbar.add(zoomIn);

        
        JButton zoomOut = new JButton();
        zoomOut.setIcon(new ImageIcon("icons/ZoomOut24.gif"));
        zoomOut.setActionCommand("ZOOM.OUT");
        zoomOut.addActionListener(zoomListener);
        zoomOut.setToolTipText("Zoom Out");
        toolbar.add(zoomOut);

        toolbar.add(Box.createVerticalStrut(10));
     
        toolbar.setFloatable(false);
//        toolbar.add(Box.createVerticalGlue());
        toolbar.add(Box.createVerticalStrut(40));
        
        add(toolbar, BorderLayout.EAST);
	}
	
	/**
	 * Create new NeuronView. Use openFile(File f) to view file.
	 * 
	 * @param title
	 */
	public J3DView() {

		setLayout(new BorderLayout());
		
		initJ3D();
		
		createToolbar();
		
		addListeners();
	}

	/**
	 * Create scene graph; background and scaling transformation.
	 * @return
	 */
    private BranchGroup createSceneGraph()
    {
        BranchGroup root = new BranchGroup();
        root.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        root.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
        root.setCapability(BranchGroup.ALLOW_DETACH);
        
        // White background
        Background bg = new Background(new Color3f(Color.white));
        bg.setApplicationBounds(new BoundingSphere());
        root.addChild(bg);

        // Scale view
        Transform3D transform = new Transform3D();
        transform.setScale(1.0/1000.0);
        
/*        Transform3D rot = new Transform3D();
        rot.rotX(Math.PI*0.5);
        rot.rotZ(Math.PI*0.8);
        scale.mul(rot);*/
        
        
        scene = new TransformGroup(transform);
        scene.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
        scene.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
        
//        scene.addChild(new ColorCube(40f));
        
        root.addChild(scene);


        //assemble scene graph
        // ...
     
        root.compile();
        return root;
    }

    /**
     * Create view of scene. 
     * @param canvas3D
     * @return
     */
    private BranchGroup createView(Canvas3D canvas3D)
    {
    	//5. Initialize view branch
    	BranchGroup viewBG = new BranchGroup();
        viewBG.setCapability(BranchGroup.ALLOW_DETACH);
        
    	viewTG = new TransformGroup();
    	viewTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    	viewTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    	viewBG.addChild(viewTG);

        ViewPlatform viewPlatform = new ViewPlatform();
    	viewTG.addChild(viewPlatform);

    	//6. Move the view branch to view object at origin
//    	Vector3f transV = new Vector3f(0f, 0.6f, 2f);  // *** !!! Move camera
    	Transform3D viewTransform = new Transform3D();
    	viewTransform.setTranslation(cameraPos);
//    	viewTransform.setScale(scale);    	
    	viewTG.setTransform(viewTransform);
    	
    	//7. Construct view for myViewPlatform
    	View view = new View();
    	view.addCanvas3D(canvas3D);
    	view.setPhysicalBody(new PhysicalBody());
    	view.setPhysicalEnvironment(new PhysicalEnvironment());
    	view.attachViewPlatform(viewPlatform);

    	return viewBG;
    }

  
	
	public BranchGroup addPlane(double y, Color color, float weight, int pattern)
	{
		return addPlane(y, color, weight, pattern, null, null);
	}
	
	public BranchGroup addPlane(double y, Color color, float weight, int pattern, String annotation, Color textColor)
	{
		BranchGroup bg = new BranchGroup();
        bg.setCapability(BranchGroup.ALLOW_DETACH);
        
		LineArray plane = new LineArray(2, GeometryArray.COORDINATES);
		plane.setCoordinate(0, new double[] { -1000.0, y, 0.0 });
		plane.setCoordinate(1, new double[] { 1000.0, y, 0.0 });

		Shape3D line = new Shape3D(plane);
        Appearance app = new Appearance();
        app.setColoringAttributes(new ColoringAttributes(new Color3f(color), ColoringAttributes.SHADE_FLAT));
        app.setLineAttributes(new LineAttributes(weight, pattern, true));
        line.setAppearance(app);
        bg.addChild(line);
        
        if (annotation != null) {
        	Text2D ann = new Text2D(annotation, new Color3f(textColor), "Helvetica", 24, Font.BOLD);
        	ann.setRectangleScaleFactor(1);
        	
        	Appearance textApp = ann.getAppearance();
        	PolygonAttributes poly = new PolygonAttributes();
        	poly.setCullFace(PolygonAttributes.CULL_NONE);
        	poly.setBackFaceNormalFlip(true);
        	textApp.setPolygonAttributes(poly); 
        	
        	Transform3D tx = new Transform3D();
        	tx.setTranslation(new Vector3d(new double[] { -500.0, y, 0.0 }));
        	TransformGroup tg = new TransformGroup(tx);
        	//tg.addChild(new ColorCube(10.0));
        	tg.addChild(ann);
        	bg.addChild(tg);
        }
        
        bg.compile();
        return bg;
	}

	protected BranchGroup addVertPlane(double x, Color color, float weight, int pattern, String annotation, Color textColor)
	{
		BranchGroup bg = new BranchGroup();
        bg.setCapability(BranchGroup.ALLOW_DETACH);
        
		LineArray plane = new LineArray(2, GeometryArray.COORDINATES);
		plane.setCoordinate(0, new double[] { x, -1000.0, 0.0 });
		plane.setCoordinate(1, new double[] { x, 1000.0, 0.0 });

		Shape3D line = new Shape3D(plane);
        Appearance app = new Appearance();
        app.setColoringAttributes(new ColoringAttributes(new Color3f(color), ColoringAttributes.SHADE_FLAT));
        app.setLineAttributes(new LineAttributes(weight, pattern, true));
        line.setAppearance(app);
        bg.addChild(line);
        
        if (annotation != null) {
        	Text2D ann = new Text2D(annotation, new Color3f(textColor), "Helvetica", 24, Font.BOLD);
        	ann.setRectangleScaleFactor(1);
        	
        	Appearance textApp = ann.getAppearance();
        	PolygonAttributes poly = new PolygonAttributes();
        	poly.setCullFace(PolygonAttributes.CULL_NONE);
        	poly.setBackFaceNormalFlip(true);
        	textApp.setPolygonAttributes(poly); 
        	
        	Transform3D tx = new Transform3D();
        	tx.setTranslation(new Vector3d(new double[] { x, -500, 0.0 }));
        	TransformGroup tg = new TransformGroup(tx);
        	//tg.addChild(new ColorCube(10.0));
        	tg.addChild(ann);
        	bg.addChild(tg);
        }
        
        bg.compile();
        return bg;
	}

    
}
